package com.syf.service.impl;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.RandomUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.syf.domain.CashRechargeAuditRecord;
import com.syf.domain.Coin;
import com.syf.domain.Config;
import com.syf.dto.AdminBankDto;
import com.syf.dto.UserDto;
import com.syf.feign.AdminBankServiceFeign;
import com.syf.feign.UserServiceFeign;
import com.syf.mapper.CashRechargeAuditRecordMapper;
import com.syf.model.CashParam;
import com.syf.service.AccountService;
import com.syf.service.CoinService;
import com.syf.service.ConfigService;
import com.syf.vo.CashTradeVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.syf.domain.CashRecharge;
import com.syf.mapper.CashRechargeMapper;
import com.syf.service.CashRechargeService;
import org.springframework.util.CollectionUtils;

/**
*
*
*@author 苏耀奋
*@create 2021-10-12 18:48
*/
@Slf4j
@Service
public class CashRechargeServiceImpl extends ServiceImpl<CashRechargeMapper, CashRecharge> implements CashRechargeService{

    @Autowired
    private UserServiceFeign userServiceFeign;   //远程调用 该接口中的方法


    //缓存 分布式锁
    @CreateCache(name = "CASH_RECHARGE_LOCK:", expire = 100, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.BOTH)
    private Cache<String, String> cache;

    @Autowired
    private CashRechargeAuditRecordMapper cashRechargeAuditRecordMapper;  //现金 充值审核

    @Autowired
    private AccountService accountService; //用户财产


    @Autowired
    private ConfigService configService;   // 币种配置

    @Autowired
    private AdminBankServiceFeign adminBankServiceFeign;

    @Autowired
    private CoinService coinService;  //币种

    @Autowired
    private Snowflake snowflake; //雪花算法 -- ID



    /**
     * 1.分页条件查询充值记录  -- 最多进行一次远程调用
     *
     * @param page      分页参数
     * @param coinId    币种的Id
     * @param userId    用户的Id     1014347296476905474 (很多数据记录 不完善的)
     * @param userName  用户的名称
     * @param mobile    用户的手机号
     * @param status    充值的状态
     * @param numMin    充值的最小金额
     * @param numMax    充值的最大金额
     * @param startTime 充值的开始时间
     * @param endTime   充值的截至时间
     * @return
     */
    @Override
    public Page<CashRecharge> findByPage(Page<CashRecharge> page, Long coinId, Long userId, String userName, String mobile, Byte status, String numMin, String numMax, String startTime, String endTime) {
        LambdaQueryWrapper<CashRecharge> wrapper = new LambdaQueryWrapper<>();

        // 1 若用户本次的查询中,带了用户的信息userId, userName,mobile ----> 本质就是: 要使用 用户Id 去查询(用户信息、GCN充值记录)
        Map<Long, UserDto> baseUsers = null;
        if(userId != null || StringUtils.isNotEmpty(userName) || StringUtils.isNotEmpty(mobile)){
            // 需要远程调用查询用户的信息 (需要添加并启动 member的api模块)
            baseUsers = userServiceFeign.getBasicUsers(userId == null ? null : Arrays.asList(userId), userName, mobile);
            if(CollectionUtils.isEmpty(baseUsers)){  //找不到 指定用户
                return page;  //返回空数据(避免了 in()范围查询 参数为null忽略此条件)
            }
            Set<Long> longs = baseUsers.keySet(); //获取Map中的Key名称（并进行去重）
            wrapper.in(!CollectionUtils.isEmpty(longs), CashRecharge::getUserId, longs);   //根据 用户ID 查询

        }

        // 2 其他的查询条件
        wrapper.eq(coinId != null, CashRecharge::getCoinId, coinId) //币种类型
                .eq(status != null, CashRecharge::getStatus, status )  //状态（待审核、审核通过、拒绝、充值成功）
                .between( //充值金币范围
                        !(StringUtils.isEmpty(numMin) || StringUtils.isEmpty(numMax)), //只要有一个为null，都不执行
                        CashRecharge::getNum,  //充值金额
                        new BigDecimal(StringUtils.isEmpty(numMin)? "0" : numMin),
                        new BigDecimal(StringUtils.isEmpty(numMax)? "0" : numMax) )

                .between(  //开始/结束时间
                        !(StringUtils.isEmpty(startTime) || StringUtils.isEmpty(endTime)),
                        CashRecharge::getCreated,
                        startTime,endTime + "23:23:59" );

        //3 分页查询
        Page<CashRecharge> cashRechargePage = page(page, wrapper);


        //4 填充 用户名称 和 用户真实名字
        List<CashRecharge> records = cashRechargePage.getRecords();
        if(!CollectionUtils.isEmpty(records)){
            List<Long> userIds = records.stream().map(CashRecharge::getUserId).collect(Collectors.toList());
            //没有使用  用户信息userName,mobile 去查询，那就需要根据userId去查询
            if(baseUsers == null){
                baseUsers = userServiceFeign.getBasicUsers(userIds, null, null);
            }

            // 设置用户相关的数据
            Map<Long, UserDto> finalBaseUsers = baseUsers;
            records.forEach(cashRecharge -> {   //代表集合中元素的 临时变量
                UserDto userDto = finalBaseUsers.get(cashRecharge.getUserId());  //获取每一条记录
                if (userDto != null) {
                    cashRecharge.setUsername(userDto.getUsername()); // 远程调用查询用户的信息
                    cashRecharge.setRealName(userDto.getRealName());
                }
            });
        }

        return cashRechargePage;  //若为null, 也返回null
    }



    /**
     * 2.现金的充值审核：
     *      充值表： CashRecharge
     *      充值审核表： CashRechargeAuditRecord
     *      账户账单详细表(入账、出账)： AccountDetail
     *      用户账单表： Account
     *
     * redis 分布式锁：
     *      CASH_RECHARGE_LOCK:  1231123
     *      只有在redis中获取到 锁"1231123"，才可以执行{}中的代码。设置获取锁的超时时间
     *
     * @param userId                  审核人
     * @param cashRechargeAuditRecord 审核的数据
     * @return 是否审核成功
     */
    @Override
    public boolean cashRechargeAudit(Long userId, CashRechargeAuditRecord cashRechargeAuditRecord) { //只有 审核Id、状态、备注
        //1 当一个员工审核时,另一个员工不能在审核 【redis 分布式锁】   CASH_RECHARGE_LOCK:  1231123
        boolean tryLockAndRun = cache.tryLockAndRun(cashRechargeAuditRecord.getId() + "", 300, TimeUnit.SECONDS, () -> {

            //2.校验
            Long rechargeId = cashRechargeAuditRecord.getId(); //提款id
            CashRecharge cashRecharge = getById(rechargeId); //充值表【账户 每次充值，都添加一条充值信息】
            if(cashRecharge == null){
                throw new IllegalArgumentException("充值记录不存在");
            }

            Byte status = cashRecharge.getStatus();   //充值状态：0-待审核；1-审核通过；2-拒绝；3-充值成功；
            if (status == 1) {
                throw new IllegalArgumentException("此次充值审核记录 已经通过");
            }
           /* if (status != 0) {
                throw new IllegalArgumentException("此次充值审核记录 已经被处理");
            }*/


            // 3.保存审核记录 (还差 充值订单号)
            CashRechargeAuditRecord cashRechargeAuditRecordDb = new CashRechargeAuditRecord();  //充值审核表
            cashRechargeAuditRecordDb.setAuditUserId(userId); //审核人
            cashRechargeAuditRecordDb.setStatus(cashRechargeAuditRecord.getStatus()); //审核状态：  审核成功/拒绝
            cashRechargeAuditRecordDb.setRemark(cashRechargeAuditRecord.getRemark()); //审核备注
            Integer step = cashRecharge.getStep() + 1; //当前审核级数
            cashRechargeAuditRecordDb.setStep(step.byteValue());

            int insert = cashRechargeAuditRecordMapper.insert(cashRechargeAuditRecordDb);
            if (insert == 0) {
                throw new IllegalArgumentException("审核记录保存失败");
            }


            //4.审核过后 修改 用户账户表和充值表【通过:给用户的账户充值   拒绝:修改充值表 状态和备注】
            cashRecharge.setStatus(cashRechargeAuditRecord.getStatus());
            cashRecharge.setAuditRemark(cashRechargeAuditRecord.getRemark());
            cashRecharge.setStep(step.byteValue());

            if (cashRechargeAuditRecord.getStatus() == 2) {
                updateById(cashRecharge);   // 拒绝

            } else {  // 审核通过,

                /**
                 * 给用户账户的余额 增加金额：
                 *      1.添加一条入账记录 AccountDetail
                 *      2.给 用户账户 增加 币种数量
                 *
                 * AccountDetail参数：【审核人、充值人、充值的币种ID、充值id、充值数量、手续费、流水状态、业务类型、当前审核级数】
                 */
                Boolean isOk = accountService.transferAccountAmount(userId,cashRecharge.getUserId(),
                        cashRecharge.getCoinId(),cashRecharge.getId(), cashRecharge.getNum(), cashRecharge.getFee(),
                        "充值", "recharge_into",(byte)1);

                // 设置 充值完成时间
                if (isOk) {
                    cashRecharge.setLastTime(new Date());
                    updateById(cashRecharge);
                }
            }
        });

        return tryLockAndRun; //是否获取到锁
    }




    /**
     * 3.查询当前用户的充值的数据 (用户提现记录 + 用户充值记录)
     *
     * @param page   分页对象
     * @param userId 用户的Id
     * @param status 订单的状态
     * @return
     */
    @Override
    public Page<CashRecharge> findUserCashRecharge(Page<CashRecharge> page, Long userId, Byte status) {
        return page(page, new LambdaQueryWrapper<CashRecharge>()
                .eq(CashRecharge::getUserId, userId)
                .eq(status != null, CashRecharge::getStatus, status)
        );
    }



    /**
     * 4.进行一个GCN/充值/购买 【用户充值 -> 添加一个cashRecharge(充值记录) -> 充值审核 】   人民币(银行卡) -> GCN
     *      注意：
     *          这儿没有处理 银行卡余额的扣减和增加【只关注 Account用户账户币种的数量 和 实际的交易金额】
     *
     *
     * @param userId    用户的id
     * @param cashParam 现金参数
     * @return
     */
    @Override
    public CashTradeVo buy(Long userId, CashParam cashParam) {
        //1 校验现金参数(充值的数量不能小于100)
        checkCashParam(cashParam);

        // 2 查询我们公司的所有银行卡(公司收款的银行卡，多个)
        List<AdminBankDto> allAdminBanks = adminBankServiceFeign.getAllAdminBanks();  //导入  admin-api

        // 仅仅需要一张银行卡 生成一个随机的int值，该值介于[0,n)的区间，也就是0到n之间的随机int值，包含0而不包含n
        AdminBankDto adminBankDto = loadbalancer(allAdminBanks);

        //3 生成订单号\参考号
        String orderNo = String.valueOf(snowflake.nextId());   //雪花算法
        String remark = RandomUtil.randomNumbers(6);    //获取6个随机数字

        Coin coin = coinService.getById(cashParam.getCoinId());  //币种
        if (coin == null) {
            throw new IllegalArgumentException("coinId不存在");
        }

        //总金额cashParam.getMum()：   人民币 -> GCN   1:1    【提现： GCN -> 人民币   0.99   亏】
        //这是前端给我们的金额,前端可能因为浏览器的缓存导致价格不准确,因此,我们需要在后台进行计算
        Config buyGCNRate = configService.getConfigByCode("CNY2USDT");
        //总金额 = 数量 * 兑换率(1.00)  multiply(): 乘以   setScale(): BigDecimal类型 小数点后保留2位，之后的 四舍五入  2.225 -> 2.23
        BigDecimal realMum = cashParam.getMum().multiply(new BigDecimal(buyGCNRate.getValue())).setScale(2, RoundingMode.HALF_UP);

        // 4 在数据库里面插入一条充值的记录
        CashRecharge cashRecharge = new CashRecharge();
        cashRecharge.setUserId(userId);

        cashRecharge.setName(adminBankDto.getName());   // 银行卡的信息
        cashRecharge.setBankName(adminBankDto.getBankName());
        cashRecharge.setBankCard(adminBankDto.getBankCard());

        cashRecharge.setTradeno(orderNo);
        cashRecharge.setCoinId(cashParam.getCoinId());
        cashRecharge.setCoinName(coin.getName());
        cashRecharge.setNum(cashParam.getNum());
        cashRecharge.setMum(realMum); // 实际的交易金额
        cashRecharge.setRemark(remark);
        cashRecharge.setFee(BigDecimal.ZERO);
        cashRecharge.setType("linepay"); // 在线支付
        cashRecharge.setStatus((byte) 0); // 待审核
        cashRecharge.setStep((byte) 1);// 第一步
        boolean save = save(cashRecharge);  //充值成功后，等待审核

        if (save) {
            // 5 返回我们的成功对象
            CashTradeVo cashTradeVo = new CashTradeVo();
            cashTradeVo.setAmount(realMum);  // 我们收户的银行卡信息
            cashTradeVo.setStatus((byte) 0);
            cashTradeVo.setName(adminBankDto.getName());
            cashTradeVo.setBankName(adminBankDto.getBankName());
            cashTradeVo.setBankCard(adminBankDto.getBankCard());
            cashTradeVo.setRemark(remark);
            return cashTradeVo;
        }
        return null;
    }

    /**
     * 4.1校验现金参数(充值的数量 不能小于 最小充值数(100))
     * @param cashParam
     */
    private void checkCashParam(CashParam cashParam) {
        @NotNull BigDecimal num = cashParam.getNum();  //此次充值的数量

        Config withDrowConfig = configService.getConfigByCode("WITH_DROW"); //根据code代码 获取指定 全局配置信息
        @NotBlank String value = withDrowConfig.getValue();  //配置值
        BigDecimal minRecharge = new BigDecimal(value);  //规定的最小充值数： 100

        if(num.compareTo(minRecharge) < 0){
            throw new IllegalArgumentException("充值数量太小");
        }
    }

    /**
     * 4.2多张银行卡中 随机选取一张
     * @param allAdminBanks
     * @return
     */
    private AdminBankDto loadbalancer(List<AdminBankDto> allAdminBanks) {
        if(CollectionUtils.isEmpty(allAdminBanks)){
            throw new RuntimeException("没有发现可用的银行卡");
        }
        int size = allAdminBanks.size();
        if(size == 1){
            return  allAdminBanks.get(0);
        }

        Random random = new Random();
        return allAdminBanks.get(random.nextInt(size));  // 随机从[0, size)中选一个
    }




}
